﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

namespace Microsoft.Media.ISO
{
    /// <summary>
    /// Implements a box binary stream reader
    /// </summary>
    internal class BoxBinaryReader : BinaryReader
    {
        static readonly DateTime offsetDate = new DateTime(1904, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        /// <summary>
        /// Initializes a new instance of the <see cref="BoxBinaryReader"/> class.
        /// </summary>
        /// <param name="inputStream">The input stream.</param>
        public BoxBinaryReader(Stream inputStream)
            : base(inputStream, Encoding.UTF8)
        { }

#if NETFX_CORE
        /// <summary>
        /// Initializes a new instance of the <see cref="BoxBinaryReader"/> class.
        /// </summary>
        /// <param name="inputStream">The input stream.</param>
        /// <param name="leaveOpen">true to leave the stream open after the BoxBinaryReader object is disposed; otherwise, false.</param>
        public BoxBinaryReader(Stream inputStream, bool leaveOpen)
            : base(inputStream, Encoding.UTF8, leaveOpen)
        { }
#endif

        /// <summary>
        /// Gets the current offset of the stream.
        /// </summary>
        public ulong Offset { get { return (ulong)this.BaseStream.Position; } }

        /// <summary>
        /// Tells whether this <see cref="BoxBinaryReader"/> is at end of stream.
        /// </summary>
        /// <value>
        ///   <c>true</c> if at end of stream; otherwise, <c>false</c>.
        /// </value>
        public bool AtEndOfStream { get { return !this.BaseStream.CanSeek || this.BaseStream.Position == this.BaseStream.Length; } }

        /// <summary>
        /// Reads characters from the stream until it finds the '\0' termination.
        /// </summary>
        /// <returns></returns>
        public string ReadNullTerminatedString()
        {
            char ch;
            StringBuilder builder = new StringBuilder();
            while ((ch = this.ReadChar()) != '\0')
            {
                builder.Append(ch);
            }
            return builder.ToString();
        }

        /// <summary>
        /// Reads a GUID from the stream.
        /// </summary>
        public Guid ReadGuid()
        {
            uint a = this.ReadUInt32();
            ushort b = this.ReadUInt16();
            ushort c = this.ReadUInt16();
            byte[] buffer = this.ReadBytes(8);
            return new Guid((int)a, (short)b, (short)c, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
        }

        /// <summary>
        /// Convert an offset of seconds to the corresponding UTC datetime.
        /// </summary>
        /// <param name="numberOfSeconds">The number of seconds.</param>
        public DateTimeOffset ReadDateTimeOffsetUtc32()
        {
            ulong numberOfSeconds = this.ReadUInt32();
            return offsetDate.AddSeconds(numberOfSeconds);
        }

        /// <summary>
        /// Convert an offset of seconds to the corresponding UTC datetime.
        /// </summary>
        /// <param name="numberOfSeconds">The number of seconds.</param>
        public DateTimeOffset ReadDateTimeOffsetUtc64()
        {
            ulong numberOfSeconds = this.ReadUInt64();
            return offsetDate.AddSeconds(numberOfSeconds);
        }

        /// <summary>
        /// Reads a string from the stream with the specified number of characters.
        /// </summary>
        /// <param name="numChars">The number of characters.</param>
        /// <returns></returns>
        public string ReadString(int numChars)
        {
            return new string(this.ReadChars(numChars));
        }

        /// <summary>
        /// Reads a 2-byte unsigned integer from the current stream and advances the position of the stream by two bytes.
        /// </summary>
        /// <returns>
        /// A 2-byte unsigned integer read from this stream.
        /// </returns>
        /// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public override ushort ReadUInt16()
        {
            byte num1 = this.ReadByte();
            byte num2 = this.ReadByte();
            return (ushort)((num1 << 8) + num2);
        }

        /// <summary>
        /// Reads a 3-byte unsigned integer from the current stream and advances the position of the stream by four bytes.
        /// </summary>
        /// <returns>
        /// A 3-byte unsigned integer read from this stream.
        /// </returns>
        /// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>        
        public uint ReadUInt24()
        {
            uint num1 = this.ReadByte();
            uint num2 = this.ReadByte();
            uint num3 = this.ReadByte();
            return (((num1 << 8) + num2) << 8) + num3;
        }

        /// <summary>
        /// Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes.
        /// </summary>
        /// <returns>
        /// A 4-byte unsigned integer read from this stream.
        /// </returns>
        /// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public override uint ReadUInt32()
        {
            uint num1 = this.ReadByte();
            uint num2 = this.ReadByte();
            uint num3 = this.ReadByte();
            uint num4 = this.ReadByte();
            return (uint)((((((num1 << 8) + num2) << 8) + num3) << 8) + num4);
        }

        /// <summary>
        /// Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.
        /// </summary>
        /// <returns>
        /// An 8-byte unsigned integer read from this stream.
        /// </returns>
        /// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public override ulong ReadUInt64()
        {
            ulong num1 = this.ReadUInt32();
            ulong num2 = this.ReadUInt32();
            return ((num1 << 0x20) + num2);
        }

        public uint ReadIntValueFromBytes(byte numberOfBytes)
        {
            switch (numberOfBytes)
            {
                case 0:
                    return ReadByte();
                case 1:
                    return ReadUInt16();
                case 2:
                    return ReadUInt24();
                case 3:
                    return ReadUInt32();
                default:
                    throw new ArgumentOutOfRangeException("numberOfBytes");
            }
        }

        /// <summary>
        /// Reads an unsigned 16 bit integer from the current stream and converts it to three characters and advances
        /// the position of the stream by four bytes.
        /// </summary>
        /// <returns>Returns a string of three characters read and unpacked from the stream.</returns>
        /// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public string ReadStringFromUShort()
        {
            UInt16 charBits = this.ReadUInt16();
            char[] unpackedChars = new char[3];

            for (int i = 0; i < 3; i++)
            {
                int c = (charBits >> (2 - i) * 5) & 0x1f;
                unpackedChars[i] = (char)(c + 0x60);
            }

            return new string(unpackedChars);
        }
    }
}
